/* Copyright (c) 2019 285424336@qq.com.
 * All rights reserved.
 * Author: 285424336
 * License: LGPLV3
 */
#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>

#include <iostream>
#include <cstdlib>
#include <memory>
#include <fcntl.h>
#include <errno.h>

#ifdef G_OS_WIN32
#include <io.h>
#ifdef USE_VLD
#include <vld.h>
#endif
#endif

static std::shared_ptr<GMainLoop> main_loop;
const gchar* file_name = "test.txt";
const gchar* directory_name = "test";

static void monitor_changed(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type, gpointer user_data);
static void create_file();
static void test_file();
static void write_file();
static void read_file();
static void delete_file();
static void create_directory();
static void test_directory();
static void traverse_directory();
static void delete_directory();

int main(int argc, char** argv) {
    (void)argc;
    (void)argv;
    main_loop.reset(g_main_loop_new(nullptr, FALSE), [](GMainLoop* loop) {
        if (nullptr != loop) {
            g_main_loop_unref(loop);
        }
    });
    g_assert_nonnull(main_loop.get());

    GFile* file = g_file_new_for_path(file_name);
    g_assert_nonnull(file);
    GCancellable* cancellable = g_cancellable_new();
    g_assert_nonnull(cancellable);
    GError* error = nullptr;
    GFileMonitor* file_monitor = g_file_monitor_file(file,
                                                     G_FILE_MONITOR_SEND_MOVED,
                                                     cancellable,
                                                     &error);
    g_assert_nonnull(file_monitor);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
    }
    g_file_monitor_set_rate_limit(file_monitor, 1000);
    g_signal_connect(static_cast<gpointer>(file_monitor),
                     "changed",
                     G_CALLBACK(monitor_changed),
                     nullptr);

    create_file();
    test_file();
    write_file();
    read_file();
    delete_file();

    create_directory();
    test_directory();
    traverse_directory();
    delete_directory();

    g_main_loop_run(main_loop.get());
    g_clear_object(reinterpret_cast<gpointer*>(&cancellable));
    g_clear_object(reinterpret_cast<gpointer*>(&file));
    g_clear_object(reinterpret_cast<gpointer*>(&file_monitor));
    return EXIT_SUCCESS;
}

static void monitor_changed(GFileMonitor* monitor,
                            GFile* file,
                            GFile* other_file,
                            GFileMonitorEvent event_type,
                            gpointer user_data) {
    (void)monitor;
    (void)other_file;
    (void)user_data;
    gchar* path = nullptr;
    path = g_file_get_path(file);
    if (nullptr != path) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "event_type = %d, path = %s\n", event_type, path);
        g_free(path);
        path = nullptr;
    }
//    g_main_loop_quit(main_loop.get());
}

static void create_file() {
    int file_descriptor = -1;
    do {
        gboolean boolean = FALSE;
        // 如果测试的任何位字段为真，则返回TRUE。
        // 例如，如果文件存在则(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)将返回真；
        // 当存在测试为真时，检查是否为目录并不重要。
        // 使用当前有效测试集时，一次同时传递多个是没有意义的。
        // 除了G_FILE_TEST_IS_SYMLINK外所有测试都跟随符号链接，
        // 因此常规文件的符号链接将g_file_test()对于G_FILE_TEST_IS_SYMLINK和G_FILE_TEST_IS_REGULAR都返回TRUE。
        // 注意，野链接g_file_test()的 G_FILE_TEST_IS_SYMLINK将返回TRUE，其它返回FALSE。
        // 你不应该使用g_file_test来测试执行一个操作是否安全，因为在你实际执行操作之前总会存在改变条件的可能。
        // 例如，你可能认为你可以使用G_FILE_TEST_IS_SYMLINK来知道写入文件是否安全而不会被欺骗成写入不同位置。
        // 但这不管用。
        // 不要这样做
        // if (!g_file_test(filename, G_FILE_TEST_IS_SYMLINK)) {
        //     fd = g_open(filename, O_WRONLY);
        //     // write to fd
        // }
        // 另一件需要注意的是，G_FILE_TEST_EXISTS和G_FILE_TEST_IS_EXECUTABLE是通过access()系统调用实现的。
        // 这通常是没问题的，但如果你的程序是setuid或者setgid的话，这意味着这些测试将给你真实用户ID和组ID，而不是有效用户ID和组ID。
        boolean = g_file_test(file_name, G_FILE_TEST_IS_REGULAR);
        if (boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s file is already exists!\n", file_name);
            break;
        }
        file_descriptor = g_open(file_name,
                                 O_CREAT | O_RDWR,
                                 0777);
        if (-1 == file_descriptor) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "-1 == file_descriptor, file_error = %d\n", file_error);
            break;
        }
    } while (0);
    if (-1 != file_descriptor) {
        GError* error = nullptr;
        gboolean boolean = g_close(file_descriptor, &error);
        if (!boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean\n");
            if (nullptr != error) {
                g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
                g_error_free(error);
                error = nullptr;
            }
        }
    }
}

static void test_file() {
    gboolean boolean = FALSE;
    boolean = g_file_test(file_name, G_FILE_TEST_IS_REGULAR);
    if (boolean) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_REGULAR\n", file_name);
    }
    boolean = g_file_test(file_name, G_FILE_TEST_IS_SYMLINK);
    if (boolean) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_SYMLINK\n", file_name);
    }
    boolean = g_file_test(file_name, G_FILE_TEST_IS_EXECUTABLE);
    if (boolean) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_EXECUTABLE\n", file_name);
    }
    boolean = g_file_test(file_name, G_FILE_TEST_IS_DIR);
    if (boolean) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_DIR\n", file_name);
    }
}

static void write_file() {
    FILE* file = nullptr;
    do {
        gboolean boolean = FALSE;
        boolean = g_file_test(file_name, G_FILE_TEST_IS_REGULAR);
        if (!boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean\n");
            break;
        }
        file = g_fopen(file_name, "w+");
        if (nullptr == file) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr == file, file_error = %d\n", file_error);
            break;
        }
        const gchar* str = "hello";
        size_t written = fwrite(static_cast<void const*>(str),
                                sizeof(gchar),
                                strlen(str),
                                file);
        if (static_cast<size_t>(-1) == written) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "-1 == written, file_error = %d\n", file_error);
            break;
        }
        fflush(file);
    } while (0);
    if (nullptr != file) {
        int ret = fclose(file);
        if (0 != ret) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "0 != ret, file_error = %d\n", file_error);
        }
        file = nullptr;
    }
}

static void read_file() {
    FILE* file = nullptr;
    do {
        gboolean boolean = FALSE;
        boolean = g_file_test(file_name, G_FILE_TEST_IS_REGULAR);
        if (!boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean\n");
            break;
        }
        file = g_fopen(file_name, "r");
        if (nullptr == file) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "file_error = %d\n", file_error);
            break;
        }
        gchar str[7] = {0};
        size_t wanted_count = sizeof(str) / sizeof(str[0]);
        size_t read_cout = fread(static_cast<void*>(str),
                                 sizeof(str[0]),
                wanted_count,
                file);
        if (0 == read_cout) {
            if (ferror(file)) {
                g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "ferror(file)\n");
                GFileError file_error = g_file_error_from_errno(errno);
                g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "file_error = %d\n", file_error);
            }
            break;
        }
        if (feof(file)) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "feof(file), read_cout = %d\n", read_cout);
        }
        for (size_t index = 0; index < read_cout; ++index) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%d = %c", index, str[index]);
        }
    } while (0);
    if (nullptr != file) {
        int ret = fclose(file);
        if (0 != ret) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "0 != ret, file_error = %d\n", file_error);
        }
        file = nullptr;
    }
}

static void delete_file() {
    do {
        gboolean boolean = FALSE;
        boolean = g_file_test(file_name, G_FILE_TEST_IS_REGULAR);
        if (!boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean\n");
            break;
        }
        //
        int ret = g_remove(file_name);
        if (-1 == ret) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "0 != ret, file_error = %d\n", file_error);
        }
    } while (0);
}

static void create_directory() {
    do {
        gboolean boolean = FALSE;
        boolean = g_file_test(directory_name, G_FILE_TEST_IS_DIR);
        if (boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s directory is already exists!\n", directory_name);
            break;
        }
        int ret = g_mkdir(directory_name, 0755);
        if (-1 == ret) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "0 != ret, file_error = %d\n", file_error);
        }
    } while (0);
}

static void test_directory() {
    do {
        gboolean boolean = FALSE;
        boolean = g_file_test(directory_name, G_FILE_TEST_IS_REGULAR);
        if (boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_REGULAR\n", directory_name);
        }
        boolean = g_file_test(directory_name, G_FILE_TEST_IS_SYMLINK);
        if (boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_SYMLINK\n", directory_name);
        }
        boolean = g_file_test(directory_name, G_FILE_TEST_IS_EXECUTABLE);
        if (boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_EXECUTABLE\n", directory_name);
        }
        boolean = g_file_test(directory_name, G_FILE_TEST_IS_DIR);
        if (boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "%s is G_FILE_TEST_IS_DIR\n", directory_name);
        }
    } while (0);
}

static void traverse_directory() {
    std::shared_ptr<gchar> current_dir(g_get_current_dir(), [](gchar* dir){
        g_free(static_cast<gpointer>(dir));
    });
    g_assert_nonnull(current_dir.get());
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "current_dir = %s\n", current_dir.get());
    GFile* file = g_file_new_for_path(current_dir.get());
    g_assert_nonnull(file);
    GFileEnumerator* file_enumerator = nullptr;
    do {
        GError* error = nullptr;
        file_enumerator = g_file_enumerate_children(file,
                                                    "*",
                                                    G_FILE_QUERY_INFO_NONE,
                                                    nullptr,
                                                    &error);
        if (nullptr != error) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
            g_error_free(error);
            error = nullptr;
            break;
        }
        GFileInfo* file_info = nullptr;
        do {
            if (nullptr != file_info) {
                g_clear_object(&file_info);
            }
            file_info = g_file_enumerator_next_file(file_enumerator,
                                                    nullptr,
                                                    &error);
            if (nullptr != file_info) {
                GFile* current_file = g_file_enumerator_get_child(file_enumerator, file_info);
                if (nullptr == current_file) {
                    break;
                }
                gchar* path = g_file_get_path(current_file);
                if (nullptr != path) {
                    if (G_FILE_TYPE_DIRECTORY == g_file_info_get_file_type (file_info)) {
                        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "directory path = %s\n", path);
                    } else {
                        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "file path = %s\n", path);
                    }
                    g_free(path);
                    path = nullptr;
                }
                g_clear_object(&current_file);
            }
            if (nullptr != error) {
                g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
                g_error_free(error);
                error = nullptr;
                break;
            }
        } while (nullptr != file_info);
        if (nullptr != file_info) {
            g_clear_object(&file_info);
        }
    } while (0);
    if (nullptr != file_enumerator) {
        GError* error = nullptr;
        g_file_enumerator_close(file_enumerator,
                                nullptr,
                                &error);
        if (nullptr != error) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
            g_error_free(error);
            error = nullptr;
        }
        g_clear_object(&file_enumerator);
    }
}

static void delete_directory() {
    do {
        gboolean boolean = FALSE;
        boolean = g_file_test(directory_name, G_FILE_TEST_IS_DIR);
        if (!boolean) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean\n");
            break;
        }
        int ret = g_rmdir(directory_name);
        if (0 != ret) {
            GFileError file_error = g_file_error_from_errno(errno);
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "0 != ret, file_error = %d\n", file_error);
        }
    } while (0);
}
